<?php

namespace yunj\builder;

use yunj\Validate;
use think\Response;
use think\facade\Request;

abstract class Builder {

    /**
     * 类型
     * @var string
     */
    protected $type = "";

    /**
     * 构建器验证器完全限定名称
     * @var string
     */
    protected $builderValidateClass = "";

    /**
     * 需加载的js文件路径列表
     * @var array
     */
    protected $scriptFileList = [];

    /**
     * 创建失败错误信息
     * @var array
     */
    protected $error = [];

    /**
     * 链式操作
     * 注意：按顺序执行
     * @var array
     */
    protected $options = [];

    /**
     * 链式选项回调
     * @var array
     */
    protected $optionsCallable = [];

    /**
     * 系统配置
     * @var array
     */
    protected $config = [];

    /**
     * 必填：ID
     * @var string
     */
    protected $id = "";

    /**
     * @return string
     */
    public function getType(): string {
        return $this->type;
    }

    /**
     * @return string
     */
    public function getId(): string {
        return $this->id;
    }

    /**
     * @return array
     */
    public function getScriptFileList(): array {
        return $this->scriptFileList;
    }

    /**
     * 构造函数
     * @param string $id
     * @param array $args
     */
    public function __construct(string $id = "", array $args = []) {
        $this->id = $id;
        $this->setAttr($args);
    }

    /**
     * Notes: 配置属性参数
     * Author: Uncle-L
     * Date: 2021/10/19
     * Time: 14:11
     * @param array $args
     */
    protected function setAttr(array $args = []): void {
        foreach ($args as $k => $v) {
            if (!in_array($k, $this->options)) continue;
            $this->$k($v);
        }
    }

    /**
     * Notes: 获取构建器验证器实例
     * Author: Uncle-L
     * Date: 2021/4/15
     * Time: 15:06
     * @return Validate
     */
    protected function getBuilderValidate(): Validate {
        static $validate;
        if ($validate) return $validate;
        $class = $this->builderValidateClass;
        $validate = new $class();
        return $validate;
    }

    /**
     * Notes: 设置错误信息
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 9:57
     * @param string $msg
     * @return $this
     */
    protected function error($msg = '') {
        $this->error[] = $msg;
        return $this;
    }

    /**
     * Notes: 存在错误信息
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:01
     * @return bool
     */
    public function existError() {
        return !!$this->error;
    }

    /**
     * Notes: 获取错误信息
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 9:56
     * @return string
     */
    public function getError() {
        $msg = '';
        foreach ($this->error as $v) {
            $msg .= ",{$v}";
        }
        if ($msg) $msg = substr($msg, 1);
        return $msg;
    }

    /**
     * Notes: 设置链式操作的回调集合
     * Author: Uncle-L
     * Date: 2021/10/8
     * Time: 17:58
     * @param string $option
     * @param callable $callbale
     */
    protected function setOptionCallbale(string $option, callable $callbale) {
        $optionsCallable = $this->optionsCallable && is_array($this->optionsCallable) ? $this->optionsCallable : [];
        $optionsCallable[$option] = isset($optionsCallable[$option]) ? $optionsCallable[$option] : [];
        $optionsCallable[$option][] = $callbale;
        $this->optionsCallable = $optionsCallable;
    }

    /**
     * Notes: 执行链式操作的回调集合
     * Author: Uncle-L
     * Date: 2021/10/8
     * Time: 17:59
     * @param string $want_options [想要执行的指定操作]
     */
    protected function execOptionsCallbale(string $want_options = ''): void {
        $wantOptions = !$want_options ? [] : explode(",", $want_options);
        $optionsCallable = $this->optionsCallable;
        if (!$optionsCallable || !is_array($optionsCallable)) return;
        foreach ($this->options as $option) {
            if (!isset($optionsCallable[$option]) || ($wantOptions && !in_array($option, $wantOptions))) continue;
            $callbales = $optionsCallable[$option];
            foreach ($callbales as $callbale) {
                if ($this->existError()) return;
                $optionMethod = "exec_{$option}_callable";
                $this->$optionMethod($callbale);
            }
            // 防止重复执行
            unset($this->optionsCallable[$option]);
        }
    }

    /**
     * Notes: 渲染输出
     * Author: Uncle-L
     * Date: 2021/4/15
     * Time: 13:12
     * @param \yunj\Controller $controller
     * @return Builder
     * @throws \Exception
     */
    public function assign(&$controller) {
        return Request::isAjax() ? $this->async() : $this->view($controller);
    }

    /**
     * Notes: 异步请求
     * Author: Uncle-L
     * Date: 2021/4/15
     * Time: 14:23
     * @return $this
     */
    public function async() {
        $data = input('post.');
        if (!$data || !is_array($data) || !isset($data['builderId']) || $data['builderId'] != $this->id) return $this;
        $validate = $this->getBuilderValidate();
        $res = $validate->scene('AsyncRequest')->check($data);
        if (!$res) return $this;

        $data = $validate->getData();
        $actionName = 'async_' . $data['builderAsyncType'];
        $response = $this->$actionName($data);
        $response instanceof Response ? throw_json($response) : throw_success_json($response);
        return $this;
    }

    /**
     * Notes: 视图配置渲染
     * Author: Uncle-L
     * Date: 2021/4/15
     * Time: 12:52
     * @param \yunj\Controller $controller
     * @return $this
     * @throws \Exception
     */
    protected function view(&$controller) {
        $args = $this->viewArgs();
        $view = $controller->getView();

        $type = $this->type;
        $id = $this->id;

        //构建器模板数据
        $currArgs = [$id => $args];
        $typeArgs = $view->__isset($type) ? ($view->__get($type) + $currArgs) : $currArgs;
        $view->__set($type, $typeArgs);

        // 页面 scriptFileList
        $currScriptFileList = $this->scriptFileList;
        $scriptFileList = $view->__isset("scriptFileList") ? array_unique(array_merge($view->__get("scriptFileList"), $currScriptFileList)) : $currScriptFileList;
        $view->__set('scriptFileList', $scriptFileList);

        return $this;
    }

    /**
     * Notes: 视图配置
     * Author: Uncle-L
     * Date: 2021/4/15
     * Time: 14:27
     * @return array
     * @throws \Exception
     */
    public function viewArgs() {
        $this->execOptionsCallbale();
        if ($this->existError()) throw new \Exception($this->getError());
        $args = [];
        return $args;
    }

    /**
     * Notes: 构建器ID
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:41
     * @param string $id
     * @return Builder
     */
    public function init(string $id) {
        if ($this->existError()) return $this;
        $this->id = $id;
        return $this;
    }

}